{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "3b2f2448-21c3-4196-9e61-0b47e7d0048b",
      "metadata": {
        "id": "3b2f2448-21c3-4196-9e61-0b47e7d0048b"
      },
      "source": [
        "# Editing graph state\n",
        "\n",
        "## Review\n",
        "\n",
        "We discussed motivations for human-in-the-loop:\n",
        "\n",
        "(1) `Approval` - We can interrupt our agent, surface state to a user, and allow the user to accept an action\n",
        "\n",
        "(2) `Debugging` - We can rewind the graph to reproduce or avoid issues\n",
        "\n",
        "(3) `Editing` - You can modify the state\n",
        "\n",
        "We showed how breakpoints support user approval, but don't yet know how to modify our graph state once our graph is interrupted!\n",
        "\n",
        "## Goals\n",
        "\n",
        "Now, let's show how to directly edit the graph state and insert human feedback."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "95d26b8c-d958-4d21-9ca4-4636d3dfe45c",
      "metadata": {
        "id": "95d26b8c-d958-4d21-9ca4-4636d3dfe45c"
      },
      "outputs": [],
      "source": [
        "%%capture --no-stderr\n",
        "%pip install --quiet -U langgraph langchain_google_genai langgraph_sdk"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "d5948594",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "d5948594",
        "outputId": "f673fed8-932d-49a9-9cae-25f8f0becad8"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "env: GOOGLE_API_KEY=AIzaSyDojJx56O8nzFauBKc_rj_0o7wvAnQgHAI\n",
            "env: LANGCHAIN_API_KEY=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\n"
          ]
        }
      ],
      "source": [
        "import os\n",
        "from google.colab import userdata\n",
        "\n",
        "%env GOOGLE_API_KEY = {userdata.get('GEMINI_API_KEY')}\n",
        "\n",
        "%env LANGCHAIN_API_KEY = {userdata.get('LANGCHAIN_API_KEY')}\n",
        "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
        "os.environ[\"LANGCHAIN_PROJECT\"] = \"langchain-academy\""
      ]
    },
    {
      "cell_type": "markdown",
      "id": "65a8df1f-a76a-4803-a532-ea9802106ac8",
      "metadata": {
        "id": "65a8df1f-a76a-4803-a532-ea9802106ac8"
      },
      "source": [
        "## Editing state\n",
        "\n",
        "Previously, we introduced breakpoints.\n",
        "\n",
        "We used them to interrupt the graph and await user approval before executing the next node.\n",
        "\n",
        "But breakpoints are also [opportunities to modify the graph state](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/edit-graph-state/).\n",
        "\n",
        "Let's set up our agent with a breakpoint before the `assistant` node."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "bcf24f05-ac2b-455e-846c-0c50ac86e1f4",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "bcf24f05-ac2b-455e-846c-0c50ac86e1f4",
        "outputId": "3e2ee013-661b-4820-d9bf-47b73135b00f"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "WARNING:langchain_google_genai._function_utils:Key 'title' is not supported in schema, ignoring\n",
            "WARNING:langchain_google_genai._function_utils:Key 'title' is not supported in schema, ignoring\n",
            "WARNING:langchain_google_genai._function_utils:Key 'title' is not supported in schema, ignoring\n",
            "WARNING:langchain_google_genai._function_utils:Key 'title' is not supported in schema, ignoring\n",
            "WARNING:langchain_google_genai._function_utils:Key 'title' is not supported in schema, ignoring\n",
            "WARNING:langchain_google_genai._function_utils:Key 'title' is not supported in schema, ignoring\n",
            "WARNING:langchain_google_genai._function_utils:Key 'title' is not supported in schema, ignoring\n",
            "WARNING:langchain_google_genai._function_utils:Key 'title' is not supported in schema, ignoring\n",
            "WARNING:langchain_google_genai._function_utils:Key 'title' is not supported in schema, ignoring\n"
          ]
        }
      ],
      "source": [
        "from langchain_google_genai import ChatGoogleGenerativeAI\n",
        "from langchain_core.tools import tool\n",
        "\n",
        "def multiply(a: int, b: int) -> int:\n",
        "    \"\"\"Multiply a and b.\n",
        "\n",
        "    Args:\n",
        "        a: first int\n",
        "        b: second int\n",
        "    \"\"\"\n",
        "    return a * b\n",
        "\n",
        "# This will be a tool\n",
        "def add(a: int, b: int) -> int:\n",
        "    \"\"\"Adds a and b.\n",
        "\n",
        "    Args:\n",
        "        a: first int\n",
        "        b: second int\n",
        "    \"\"\"\n",
        "    return a + b\n",
        "\n",
        "def divide(a: int, b: int) -> float:\n",
        "    \"\"\"Adds a and b.\n",
        "\n",
        "    Args:\n",
        "        a: first int\n",
        "        b: second int\n",
        "    \"\"\"\n",
        "    return a / b\n",
        "\n",
        "tools: list[tool] = [add, multiply, divide]\n",
        "llm: ChatGoogleGenerativeAI = ChatGoogleGenerativeAI(model = \"gemini-1.5-flash\")\n",
        "llm_with_tools = llm.bind_tools(tools)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 36,
      "id": "5dfe84af-5c62-4c3f-8ed7-96b5261f0b7b",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 308
        },
        "id": "5dfe84af-5c62-4c3f-8ed7-96b5261f0b7b",
        "outputId": "dae02016-48f8-4a93-cbff-a04ec34ba9f2"
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/jpeg": "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\n",
            "text/plain": [
              "<IPython.core.display.Image object>"
            ]
          },
          "metadata": {}
        }
      ],
      "source": [
        "from IPython.display import Image, display\n",
        "\n",
        "from langgraph.checkpoint.memory import MemorySaver\n",
        "from langgraph.graph import MessagesState\n",
        "from langgraph.graph import START, StateGraph\n",
        "from langgraph.prebuilt import tools_condition, ToolNode\n",
        "from langgraph.graph.state import CompiledStateGraph\n",
        "\n",
        "from langchain_core.messages import HumanMessage, SystemMessage\n",
        "\n",
        "# System message\n",
        "sys_msg = SystemMessage(content=\"You are a helpful assistant tasked with performing arithmetic on a set of inputs.\")\n",
        "\n",
        "# Node\n",
        "def assistant(state: MessagesState):\n",
        "   return {\"messages\": [llm_with_tools.invoke([sys_msg] + state[\"messages\"])]}\n",
        "\n",
        "# Graph\n",
        "builder: StateGraph = StateGraph(MessagesState)\n",
        "\n",
        "# Define nodes: these do the work\n",
        "builder.add_node(\"assistant\", assistant)\n",
        "builder.add_node(\"tools\", ToolNode(tools))\n",
        "\n",
        "# Define edges: these determine the control flow\n",
        "builder.add_edge(START, \"assistant\")\n",
        "builder.add_conditional_edges(\n",
        "    \"assistant\",\n",
        "    # If the latest message (result) from assistant is a tool call -> tools_condition routes to tools\n",
        "    # If the latest message (result) from assistant is a not a tool call -> tools_condition routes to END\n",
        "    tools_condition,\n",
        ")\n",
        "builder.add_edge(\"tools\", \"assistant\")\n",
        "\n",
        "memory: MemorySaver = MemorySaver()\n",
        "graph: CompiledStateGraph = builder.compile(interrupt_before=[\"assistant\"], checkpointer=memory)\n",
        "\n",
        "# Show\n",
        "display(Image(graph.get_graph(xray=True).draw_mermaid_png()))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "92a47fd5-1f60-41dc-9206-698ed8ece530",
      "metadata": {
        "id": "92a47fd5-1f60-41dc-9206-698ed8ece530"
      },
      "source": [
        "Let's run!\n",
        "\n",
        "We can see the graph is interrupted before the chat model responds."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 37,
      "id": "a2ce488d-00e4-492e-a62c-dd98702c313f",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "a2ce488d-00e4-492e-a62c-dd98702c313f",
        "outputId": "cfad69cc-a0da-483c-821b-d3db3d0ea485"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "================================\u001b[1m Human Message \u001b[0m=================================\n",
            "\n",
            "Multiply 2 and 3\n"
          ]
        }
      ],
      "source": [
        "# Input\n",
        "initial_input = {\"messages\": \"Multiply 2 and 3\"}\n",
        "\n",
        "# Thread\n",
        "thread = {\"configurable\": {\"thread_id\": \"1\"}}\n",
        "\n",
        "# Run the graph until the first interruption\n",
        "for event in graph.stream(initial_input, thread, stream_mode=\"values\"):\n",
        "    event['messages'][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 38,
      "id": "4be478ef-bd60-4d32-8a05-5f56c93a8396",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "4be478ef-bd60-4d32-8a05-5f56c93a8396",
        "outputId": "d9cedb71-db84-4b8c-a43c-f060e75bc7c6"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "StateSnapshot(values={'messages': [HumanMessage(content='Multiply 2 and 3', additional_kwargs={}, response_metadata={}, id='d9a556e4-f213-4f2b-a62d-5d73bd773423')]}, next=('assistant',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef7bdff-a578-6816-8000-90a2512217d4'}}, metadata={'source': 'loop', 'writes': None, 'step': 0, 'parents': {}}, created_at='2024-09-26T08:18:57.973843+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef7bdff-a571-6b65-bfff-279b2411f3af'}}, tasks=(PregelTask(id='aec1226e-b460-7d92-3107-d71377dc1149', name='assistant', path=('__pregel_pull', 'assistant'), error=None, interrupts=(), state=None),))"
            ]
          },
          "metadata": {},
          "execution_count": 38
        }
      ],
      "source": [
        "state = graph.get_state(thread)\n",
        "state"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "state.next"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "8LXVfGgiZBap",
        "outputId": "f302ad1a-d946-44c1-b204-d25065014ccc"
      },
      "id": "8LXVfGgiZBap",
      "execution_count": 39,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "('assistant',)"
            ]
          },
          "metadata": {},
          "execution_count": 39
        }
      ]
    },
    {
      "cell_type": "markdown",
      "id": "36ef63a1-2ab8-416d-babf-d35054e294f0",
      "metadata": {
        "id": "36ef63a1-2ab8-416d-babf-d35054e294f0"
      },
      "source": [
        "Now, we can directly apply a state update.\n",
        "\n",
        "Remember, updates to the `messages` key will use the `add_messages` reducer:\n",
        "\n",
        "* If we want to over-write the existing message, we can supply the message `id`.\n",
        "* If we simply want to append to our list of messages, then we can pass a message without an `id` specified, as shown below."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 40,
      "id": "9179cff1-e529-473a-9ce2-e23b932c2063",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "9179cff1-e529-473a-9ce2-e23b932c2063",
        "outputId": "d7dbbe38-eb34-4ba3-b1d8-1b6294c296c2"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{'configurable': {'thread_id': '1',\n",
              "  'checkpoint_ns': '',\n",
              "  'checkpoint_id': '1ef7be00-001c-6b0e-8001-37a798ffcfc6'}}"
            ]
          },
          "metadata": {},
          "execution_count": 40
        }
      ],
      "source": [
        "graph.update_state(\n",
        "    thread,\n",
        "    {\"messages\": [HumanMessage(content=\"No, actually multiply 3 and 3!\")]},\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d77b8d6a-8c7b-4f7a-b723-121af25ac829",
      "metadata": {
        "id": "d77b8d6a-8c7b-4f7a-b723-121af25ac829"
      },
      "source": [
        "Let's have a look.\n",
        "\n",
        "We called `update_state` with a new message.\n",
        "\n",
        "The `add_messages` reducer appends it to our state key, `messages`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 41,
      "id": "141b6aab-ec6d-44f3-beb1-6c22ac5f2158",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "141b6aab-ec6d-44f3-beb1-6c22ac5f2158",
        "outputId": "0d5df7ec-a412-4c19-913f-12e278f77f3a"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "================================\u001b[1m Human Message \u001b[0m=================================\n",
            "\n",
            "Multiply 2 and 3\n",
            "================================\u001b[1m Human Message \u001b[0m=================================\n",
            "\n",
            "No, actually multiply 3 and 3!\n"
          ]
        }
      ],
      "source": [
        "new_state = graph.get_state(thread).values\n",
        "for m in new_state['messages']:\n",
        "    m.pretty_print()"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "graph.get_state(thread)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "jaEvb2vhZj0F",
        "outputId": "9e4af299-8b52-47a0-c86d-2e43d267071d"
      },
      "id": "jaEvb2vhZj0F",
      "execution_count": 42,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "StateSnapshot(values={'messages': [HumanMessage(content='Multiply 2 and 3', additional_kwargs={}, response_metadata={}, id='d9a556e4-f213-4f2b-a62d-5d73bd773423'), HumanMessage(content='No, actually multiply 3 and 3!', additional_kwargs={}, response_metadata={}, id='e000567a-77c5-4dd9-9425-f3afd59944f9')]}, next=('assistant',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef7be00-001c-6b0e-8001-37a798ffcfc6'}}, metadata={'source': 'update', 'step': 1, 'writes': {'__start__': {'messages': [HumanMessage(content='No, actually multiply 3 and 3!', additional_kwargs={}, response_metadata={}, id='e000567a-77c5-4dd9-9425-f3afd59944f9')]}}, 'parents': {}}, created_at='2024-09-26T08:19:07.478267+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef7bdff-a578-6816-8000-90a2512217d4'}}, tasks=(PregelTask(id='d1fd448e-1ec4-69b7-a31e-a31a7b342f42', name='assistant', path=('__pregel_pull', 'assistant'), error=None, interrupts=(), state=None),))"
            ]
          },
          "metadata": {},
          "execution_count": 42
        }
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e4041959-cc3a-4168-8cf7-06d1711921d8",
      "metadata": {
        "id": "e4041959-cc3a-4168-8cf7-06d1711921d8"
      },
      "source": [
        "Now, let's proceed with our agent, simply by passing `None` and allowing it proceed from the current state.\n",
        "\n",
        "We emit the current and then proceed to execute the remaining nodes."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 44,
      "id": "f166bed2-87c9-41ec-b235-0305721c2d6b",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "f166bed2-87c9-41ec-b235-0305721c2d6b",
        "outputId": "ddb4da95-bc27-4668-867f-2218cdee2c8e"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
            "Name: multiply\n",
            "\n",
            "9\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "\n",
            "The answer is 9.\n"
          ]
        }
      ],
      "source": [
        "for event in graph.stream(None, thread, stream_mode=\"values\"):\n",
        "    event['messages'][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b18dc1ca",
      "metadata": {
        "id": "b18dc1ca"
      },
      "source": [
        "Now, we're back at the `assistant`, which has our `breakpoint`.\n",
        "\n",
        "We can again pass `None` to proceed."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 45,
      "id": "f5952731-0170-4589-a399-ee787df35400",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "f5952731-0170-4589-a399-ee787df35400",
        "outputId": "b1ab7dcf-78e5-41c3-abca-3e4cc2a59a91"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "\n",
            "The answer is 9.\n"
          ]
        }
      ],
      "source": [
        "for event in graph.stream(None, thread, stream_mode=\"values\"):\n",
        "    event['messages'][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bc22c3e9-b00c-4ead-b752-a682b45b3718",
      "metadata": {
        "id": "bc22c3e9-b00c-4ead-b752-a682b45b3718"
      },
      "source": [
        "### Editing graph state in Studio\n",
        "\n",
        "--\n",
        "\n",
        "**⚠️ DISCLAIMER**\n",
        "\n",
        "*Running Studio currently requires a Mac. If you are not using a Mac, then skip this step.*\n",
        "\n",
        "*Also, if you are running this notebook in CoLab, then skip this step.*\n",
        "\n",
        "--\n",
        "\n",
        "Let's load our `agent` in the Studio UI, which uses `module-3/studio/agent.py` set in `module-3/studio/langgraph.json`.\n",
        "\n",
        "### Editing graph state with LangGraph API\n",
        "\n",
        "We can interact with our agent via the SDK.\n",
        "\n",
        "![Screenshot 2024-08-26 at 9.59.19 AM.png](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dbaf2fbfb576f8e53ed930_edit-state-human-feedback1.png)\n",
        "\n",
        "Let's get the URL for the local deployment from Studio.\n",
        "\n",
        "The LangGraph API [supports editing graph state](https://langchain-ai.github.io/langgraph/cloud/how-tos/human_in_the_loop_edit_state/#initial-invocation)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "id": "642aabab-f822-4917-9d66-3314ac5008fd",
      "metadata": {
        "id": "642aabab-f822-4917-9d66-3314ac5008fd"
      },
      "outputs": [],
      "source": [
        "from langgraph_sdk import get_client\n",
        "client = get_client(url=\"https://cardiff-rivers-timothy-supply.trycloudflare.com\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "be74cb09",
      "metadata": {
        "id": "be74cb09"
      },
      "source": [
        "Our agent is defined in `assistant/agent.py`.\n",
        "\n",
        "If you look at the code, you'll see that it *does not* have a breakpoint!\n",
        "\n",
        "Of course, we can add it to `agent.py`, but one very nice feature of the API is that we can pass in a breakpoint!\n",
        "\n",
        "Here, we pass a `interrupt_before=[\"assistant\"]`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 26,
      "id": "1c352f9e-6a0f-4a94-a083-b85b0233efa9",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "1c352f9e-6a0f-4a94-a083-b85b0233efa9",
        "outputId": "7b163151-d92f-4e45-87aa-8f6f6112b900"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Receiving new event of type: metadata...\n",
            "--------------------------------------------------\n",
            "Receiving new event of type: values...\n",
            "{'content': 'Multiply 2 and 3', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'd7e60729-8f7f-44ba-9f74-cba9ab7124d7', 'example': False}\n",
            "--------------------------------------------------\n"
          ]
        }
      ],
      "source": [
        "initial_input = {\"messages\": \"Multiply 2 and 3\"}\n",
        "thread = await client.threads.create()\n",
        "async for chunk in client.runs.stream(\n",
        "    thread[\"thread_id\"],\n",
        "    \"agent\",\n",
        "    input=initial_input,\n",
        "    stream_mode=\"values\",\n",
        "    interrupt_before=[\"assistant\"],\n",
        "):\n",
        "    print(f\"Receiving new event of type: {chunk.event}...\")\n",
        "    messages = chunk.data.get('messages', [])\n",
        "    if messages:\n",
        "        print(messages[-1])\n",
        "    print(\"-\" * 50)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "13065dd9-5f43-47d6-ac2a-9dc15c0c54e6",
      "metadata": {
        "id": "13065dd9-5f43-47d6-ac2a-9dc15c0c54e6"
      },
      "source": [
        "We can get the current state"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 27,
      "id": "4da2c464-3e71-496a-badc-671aeee168b6",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "4da2c464-3e71-496a-badc-671aeee168b6",
        "outputId": "946990a7-e832-4d05-af9d-a27396a13d59"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{'values': {'messages': [{'content': 'Multiply 2 and 3',\n",
              "    'additional_kwargs': {},\n",
              "    'response_metadata': {},\n",
              "    'type': 'human',\n",
              "    'name': None,\n",
              "    'id': 'd7e60729-8f7f-44ba-9f74-cba9ab7124d7',\n",
              "    'example': False}]},\n",
              " 'next': ['assistant'],\n",
              " 'tasks': [{'id': '86586a11-2b9b-105e-30ec-519da709920a',\n",
              "   'name': 'assistant',\n",
              "   'path': ['__pregel_pull', 'assistant'],\n",
              "   'error': None,\n",
              "   'interrupts': [],\n",
              "   'checkpoint': None,\n",
              "   'state': None}],\n",
              " 'metadata': {'step': 0,\n",
              "  'run_id': '1ef7bdfa-e49e-68e1-b0c2-eb7d1f12bacc',\n",
              "  'source': 'loop',\n",
              "  'writes': None,\n",
              "  'parents': {},\n",
              "  'user_id': '',\n",
              "  'graph_id': 'agent',\n",
              "  'thread_id': 'cd71d1c9-5069-45f4-9516-c69a206dad1b',\n",
              "  'created_by': 'system',\n",
              "  'run_attempt': 1,\n",
              "  'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n",
              "  'x-forwarded-for': '34.171.34.224',\n",
              "  'x-forwarded-proto': 'https'},\n",
              " 'created_at': '2024-09-26T08:16:50.654718+00:00',\n",
              " 'checkpoint': {'thread_id': 'cd71d1c9-5069-45f4-9516-c69a206dad1b',\n",
              "  'checkpoint_ns': '',\n",
              "  'checkpoint_id': '1ef7bdfa-e742-6d47-8000-b0b328dd2b6f'},\n",
              " 'parent_checkpoint': {'thread_id': 'cd71d1c9-5069-45f4-9516-c69a206dad1b',\n",
              "  'checkpoint_ns': '',\n",
              "  'checkpoint_id': '1ef7bdfa-e70c-61b0-bfff-b9b7714dac77'},\n",
              " 'checkpoint_id': '1ef7bdfa-e742-6d47-8000-b0b328dd2b6f',\n",
              " 'parent_checkpoint_id': '1ef7bdfa-e70c-61b0-bfff-b9b7714dac77'}"
            ]
          },
          "metadata": {},
          "execution_count": 27
        }
      ],
      "source": [
        "current_state = await client.threads.get_state(thread['thread_id'])\n",
        "current_state"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4527bbf1-0927-41a6-aeef-d15e32bbbdc3",
      "metadata": {
        "id": "4527bbf1-0927-41a6-aeef-d15e32bbbdc3"
      },
      "source": [
        "We can look at the last message in state."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 28,
      "id": "801ae2d9-0551-46b8-aee2-82293cee4011",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "801ae2d9-0551-46b8-aee2-82293cee4011",
        "outputId": "4d215c51-ed02-4759-893e-5d91dfe244ee"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{'content': 'Multiply 2 and 3',\n",
              " 'additional_kwargs': {},\n",
              " 'response_metadata': {},\n",
              " 'type': 'human',\n",
              " 'name': None,\n",
              " 'id': 'd7e60729-8f7f-44ba-9f74-cba9ab7124d7',\n",
              " 'example': False}"
            ]
          },
          "metadata": {},
          "execution_count": 28
        }
      ],
      "source": [
        "last_message = current_state['values']['messages'][-1]\n",
        "last_message"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f0581ba8-db3d-474d-9042-b1c7f3461caf",
      "metadata": {
        "id": "f0581ba8-db3d-474d-9042-b1c7f3461caf"
      },
      "source": [
        "We can edit it!"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 29,
      "id": "86b12be7-7e4a-40d0-8521-dced7c393c71",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "86b12be7-7e4a-40d0-8521-dced7c393c71",
        "outputId": "00e7fd5c-ebb0-42ec-902a-57a64cc523bb"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{'content': 'No, actually multiply 3 and 3!',\n",
              " 'additional_kwargs': {},\n",
              " 'response_metadata': {},\n",
              " 'type': 'human',\n",
              " 'name': None,\n",
              " 'id': 'd7e60729-8f7f-44ba-9f74-cba9ab7124d7',\n",
              " 'example': False}"
            ]
          },
          "metadata": {},
          "execution_count": 29
        }
      ],
      "source": [
        "last_message['content'] = \"No, actually multiply 3 and 3!\"\n",
        "last_message"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 30,
      "id": "f84f2c24-f281-4591-90e5-de3a5547c9da",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "f84f2c24-f281-4591-90e5-de3a5547c9da",
        "outputId": "41b00f9e-6927-4697-ed71-10090480fcd4"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{'content': 'No, actually multiply 3 and 3!',\n",
              " 'additional_kwargs': {},\n",
              " 'response_metadata': {},\n",
              " 'type': 'human',\n",
              " 'name': None,\n",
              " 'id': 'd7e60729-8f7f-44ba-9f74-cba9ab7124d7',\n",
              " 'example': False}"
            ]
          },
          "metadata": {},
          "execution_count": 30
        }
      ],
      "source": [
        "last_message"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ce7b4280-6ae7-4246-9c87-44e0daa6c654",
      "metadata": {
        "id": "ce7b4280-6ae7-4246-9c87-44e0daa6c654"
      },
      "source": [
        "Remember, as we said before, updates to the `messages` key will use the same `add_messages` reducer.\n",
        "\n",
        "If we want to over-write the existing message, then we can supply the message `id`.\n",
        "\n",
        "Here, we did that. We only modified the message `content`, as shown above."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 31,
      "id": "84d33b6e-32ff-4eca-8114-345e508f3481",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "84d33b6e-32ff-4eca-8114-345e508f3481",
        "outputId": "811a26db-8728-4fa9-ab29-69ac51763d05"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{'checkpoint': {'thread_id': 'cd71d1c9-5069-45f4-9516-c69a206dad1b',\n",
              "  'checkpoint_ns': '',\n",
              "  'checkpoint_id': '1ef7bdfc-174e-67d5-8001-48f7a5f09ff0'},\n",
              " 'configurable': {'thread_id': 'cd71d1c9-5069-45f4-9516-c69a206dad1b',\n",
              "  'checkpoint_ns': '',\n",
              "  'checkpoint_id': '1ef7bdfc-174e-67d5-8001-48f7a5f09ff0'},\n",
              " 'checkpoint_id': '1ef7bdfc-174e-67d5-8001-48f7a5f09ff0'}"
            ]
          },
          "metadata": {},
          "execution_count": 31
        }
      ],
      "source": [
        "await client.threads.update_state(thread['thread_id'], {\"messages\": last_message})"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1f07f0d1-7083-4827-babd-d3702eb59a37",
      "metadata": {
        "id": "1f07f0d1-7083-4827-babd-d3702eb59a37"
      },
      "source": [
        "Now, we resume by passing `None`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 32,
      "id": "ef18d12d-e0a6-487a-9f32-ad30e2634a20",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "ef18d12d-e0a6-487a-9f32-ad30e2634a20",
        "outputId": "52a291af-3f53-49bc-eddc-435753c23e98"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Receiving new event of type: metadata...\n",
            "--------------------------------------------------\n",
            "Receiving new event of type: values...\n",
            "{'content': 'No, actually multiply 3 and 3!', 'additional_kwargs': {'additional_kwargs': {}, 'response_metadata': {}, 'example': False}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'd7e60729-8f7f-44ba-9f74-cba9ab7124d7', 'example': False}\n",
            "--------------------------------------------------\n",
            "Receiving new event of type: values...\n",
            "{'content': '', 'additional_kwargs': {'function_call': {'name': 'multiply', 'arguments': '{\"a\": 3.0, \"b\": 3.0}'}}, 'response_metadata': {'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, 'type': 'ai', 'name': None, 'id': 'run-3f0449c7-006c-4dff-acb8-68e7ad3a0c4b-0', 'example': False, 'tool_calls': [{'name': 'multiply', 'args': {'a': 3.0, 'b': 3.0}, 'id': '9f860c88-e9a5-40aa-8165-aa734530e921', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 214, 'output_tokens': 18, 'total_tokens': 232}}\n",
            "--------------------------------------------------\n",
            "Receiving new event of type: values...\n",
            "{'content': '9', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'multiply', 'id': 'b8b70f77-9266-45bc-b5bb-373f479e456b', 'tool_call_id': '9f860c88-e9a5-40aa-8165-aa734530e921', 'artifact': None, 'status': 'success'}\n",
            "--------------------------------------------------\n"
          ]
        }
      ],
      "source": [
        "async for chunk in client.runs.stream(\n",
        "    thread[\"thread_id\"],\n",
        "    assistant_id=\"agent\",\n",
        "    input=None,\n",
        "    stream_mode=\"values\",\n",
        "    interrupt_before=[\"assistant\"],\n",
        "):\n",
        "    print(f\"Receiving new event of type: {chunk.event}...\")\n",
        "    messages = chunk.data.get('messages', [])\n",
        "    if messages:\n",
        "        print(messages[-1])\n",
        "    print(\"-\" * 50)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6a82dd35-cbc8-486d-8e20-10d0c4d138d6",
      "metadata": {
        "id": "6a82dd35-cbc8-486d-8e20-10d0c4d138d6"
      },
      "source": [
        "We get the result of the tool call as `9`, as expected."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 33,
      "id": "1d1bb3c7-dc26-4c32-b3df-865f41ef3c73",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "1d1bb3c7-dc26-4c32-b3df-865f41ef3c73",
        "outputId": "68b01a2f-0745-4619-fcf2-28763b0b73b0"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Receiving new event of type: metadata...\n",
            "--------------------------------------------------\n",
            "Receiving new event of type: values...\n",
            "{'content': '9', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'multiply', 'id': 'b8b70f77-9266-45bc-b5bb-373f479e456b', 'tool_call_id': '9f860c88-e9a5-40aa-8165-aa734530e921', 'artifact': None, 'status': 'success'}\n",
            "--------------------------------------------------\n",
            "Receiving new event of type: values...\n",
            "{'content': 'The result is 9. \\n', 'additional_kwargs': {}, 'response_metadata': {'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, 'type': 'ai', 'name': None, 'id': 'run-27c2d269-1d5a-4953-8f65-7fe3a46cf029-0', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': {'input_tokens': 247, 'output_tokens': 6, 'total_tokens': 253}}\n",
            "--------------------------------------------------\n"
          ]
        }
      ],
      "source": [
        "async for chunk in client.runs.stream(\n",
        "    thread[\"thread_id\"],\n",
        "    assistant_id=\"agent\",\n",
        "    input=None,\n",
        "    stream_mode=\"values\",\n",
        "    interrupt_before=[\"assistant\"],\n",
        "):\n",
        "    print(f\"Receiving new event of type: {chunk.event}...\")\n",
        "    messages = chunk.data.get('messages', [])\n",
        "    if messages:\n",
        "        print(messages[-1])\n",
        "    print(\"-\" * 50)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6914c5ca-27e4-421c-835a-9e4327dac12f",
      "metadata": {
        "id": "6914c5ca-27e4-421c-835a-9e4327dac12f"
      },
      "source": [
        "## Awaiting user input\n",
        "\n",
        "So, it's clear that we can edit our agent state after a breakpoint.\n",
        "\n",
        "Now, what if we want to allow for human feedback to perform this state update?\n",
        "\n",
        "We'll add a node that [serves as a placeholder for human feedback](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/wait-user-input/#setup) within our agent.\n",
        "\n",
        "This `human_feedback` node allow the user to add feedback directly to state.\n",
        "\n",
        "We specify the breakpoint using `interrupt_before` our `human_feedback` node.\n",
        "\n",
        "We set up a checkpointer to save the state of the graph up until this node."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 46,
      "id": "e4b475ff-681f-4660-80dd-d6ade7bd48e3",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 407
        },
        "id": "e4b475ff-681f-4660-80dd-d6ade7bd48e3",
        "outputId": "802aedc4-cb0b-4c36-e63e-e772cf3a61ce"
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/jpeg": "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\n",
            "text/plain": [
              "<IPython.core.display.Image object>"
            ]
          },
          "metadata": {}
        }
      ],
      "source": [
        "# System message\n",
        "sys_msg = SystemMessage(content=\"You are a helpful assistant tasked with performing arithmetic on a set of inputs.\")\n",
        "\n",
        "# no-op node that should be interrupted on\n",
        "def human_feedback(state: MessagesState):\n",
        "    pass\n",
        "\n",
        "# Assistant node\n",
        "def assistant(state: MessagesState):\n",
        "   return {\"messages\": [llm_with_tools.invoke([sys_msg] + state[\"messages\"])]}\n",
        "\n",
        "# Graph\n",
        "builder: StateGraph = StateGraph(MessagesState)\n",
        "\n",
        "# Define nodes: these do the work\n",
        "builder.add_node(\"assistant\", assistant)\n",
        "builder.add_node(\"tools\", ToolNode(tools))\n",
        "builder.add_node(\"human_feedback\", human_feedback)\n",
        "\n",
        "# Define edges: these determine the control flow\n",
        "builder.add_edge(START, \"human_feedback\")\n",
        "builder.add_edge(\"human_feedback\", \"assistant\")\n",
        "builder.add_conditional_edges(\n",
        "    \"assistant\",\n",
        "    # If the latest message (result) from assistant is a tool call -> tools_condition routes to tools\n",
        "    # If the latest message (result) from assistant is a not a tool call -> tools_condition routes to END\n",
        "    tools_condition,\n",
        ")\n",
        "builder.add_edge(\"tools\", \"human_feedback\")\n",
        "\n",
        "memory = MemorySaver()\n",
        "graph = builder.compile(interrupt_before=[\"human_feedback\"], checkpointer=memory)\n",
        "display(Image(graph.get_graph().draw_mermaid_png()))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "32d4ceb6-a224-4307-8196-3f53d367df5c",
      "metadata": {
        "id": "32d4ceb6-a224-4307-8196-3f53d367df5c"
      },
      "source": [
        "We will get feedback from the user.\n",
        "\n",
        "We use `.update_state` to update the state of the graph with the human response we get, as before.\n",
        "\n",
        "We use the `as_node=\"human_feedback\"` parameter to apply this state update as the specified node, `human_feedback`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 47,
      "id": "3fc7bcd6-660c-4a8a-ad8d-e6698dcf6201",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "3fc7bcd6-660c-4a8a-ad8d-e6698dcf6201",
        "outputId": "a4d29e2b-b1c5-4229-8215-5297aca057aa"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "================================\u001b[1m Human Message \u001b[0m=================================\n",
            "\n",
            "Multiply 2 and 3\n",
            "Tell me how you want to update the state: p\n",
            "================================\u001b[1m Human Message \u001b[0m=================================\n",
            "\n",
            "p\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "Tool Calls:\n",
            "  multiply (d0b65261-8c70-4858-9976-835110063c7c)\n",
            " Call ID: d0b65261-8c70-4858-9976-835110063c7c\n",
            "  Args:\n",
            "    a: 2.0\n",
            "    b: 3.0\n",
            "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
            "Name: multiply\n",
            "\n",
            "6\n"
          ]
        }
      ],
      "source": [
        "# Input\n",
        "initial_input = {\"messages\": \"Multiply 2 and 3\"}\n",
        "\n",
        "# Thread\n",
        "thread = {\"configurable\": {\"thread_id\": \"5\"}}\n",
        "\n",
        "# Run the graph until the first interruption\n",
        "for event in graph.stream(initial_input, thread, stream_mode=\"values\"):\n",
        "    event[\"messages\"][-1].pretty_print()\n",
        "\n",
        "# Get user input\n",
        "user_input = input(\"Tell me how you want to update the state: \")\n",
        "\n",
        "# We now update the state as if we are the human_feedback node\n",
        "graph.update_state(thread, {\"messages\": user_input}, as_node=\"human_feedback\")\n",
        "\n",
        "# Continue the graph execution\n",
        "for event in graph.stream(None, thread, stream_mode=\"values\"):\n",
        "    event[\"messages\"][-1].pretty_print()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 48,
      "id": "abf4cf5f-c0cb-4fdb-be6b-271ae4e967e2",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "abf4cf5f-c0cb-4fdb-be6b-271ae4e967e2",
        "outputId": "eaeca078-a83e-4960-a2ae-b1f027f7bcb5"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
            "Name: multiply\n",
            "\n",
            "6\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "\n",
            "The answer is 6.\n"
          ]
        }
      ],
      "source": [
        "# Continue the graph execution\n",
        "for event in graph.stream(None, thread, stream_mode=\"values\"):\n",
        "    event[\"messages\"][-1].pretty_print()"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3 (ipykernel)",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.12.5"
    },
    "colab": {
      "provenance": []
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}